use std::{any::TypeId, cell::RefCell, collections::HashMap};

use super::*;

/// Information about a registered JavaScript class
pub struct ClassInfo {
	define: Reference,
	constructor: Vec<sys::napi_callback>,
}

thread_local! {
	static CLASS_REFS:RefCell<HashMap<TypeId,ClassInfo>> = Default::default();
}

/// Gets the class definition for a given type ID
pub fn class_define(id: TypeId) -> Option<Reference> {
	CLASS_REFS.with_borrow(|v| v.get(&id).map(|v| &v.define).cloned())
}

/// Gets the constructor functions for a given type ID
pub fn class_constructor(id: TypeId) -> Option<Vec<sys::napi_callback>> {
	CLASS_REFS.with_borrow(|v| v.get(&id).map(|v| &v.constructor).cloned())
}

/// Defines a JavaScript class with the given type ID
pub fn define_class(id: TypeId, define: Reference, constructor: Vec<sys::napi_callback>) {
	CLASS_REFS.with_borrow_mut(|v| {
		let old = v.insert(id, ClassInfo { define, constructor });
		if old.is_some() {
			log::error!("class redefined");
		}
	});
}

/// No-op module initialization function
pub fn module_init_noop(m: Module) -> Module {
	m
}

/// No-op module registration function
pub fn module_register_noop<'env>(_: &'env Env, e: &'env Value<'env>) -> &'env Value<'env> {
	e
}

/// Dumps TypeScript declarations for all registered components
pub fn module_dump_dts() {
	println!("/* auto-generated by napih */");
	println!("/* eslint-disable */");
	println!("/* @ts-nocheck */");
	let mut dts = DTS.iter().collect::<Vec<_>>();
	dts.sort_by(|a, b| {
		let ty_cmp = a.ty.cmp(&b.ty);
		if ty_cmp == std::cmp::Ordering::Equal {
			a.name.to_lowercase().cmp(&b.name.to_lowercase())
		} else {
			ty_cmp
		}
	});
	println!("\n/* types */\n");
	for RegDTS { func, owner, ty, .. } in dts.iter() {
		if owner.is_none() && *ty == DtsTy::Type {
			println!("{}", func());
		}
	}
	println!("\n/* consts */\n");
	for RegDTS { func, owner, ty, .. } in dts.iter() {
		if owner.is_none() && *ty == DtsTy::Const {
			println!("{}", func());
		}
	}
	println!("\n/* enums */\n");
	for RegDTS { func, owner, ty, .. } in dts.iter() {
		if owner.is_none() && *ty == DtsTy::Enum {
			println!("{}", func());
		}
	}
	println!("\n/* objects */\n");
	for RegDTS { func, owner, ty, .. } in dts.iter() {
		if owner.is_none() && *ty == DtsTy::Object {
			println!("{}", func());
		}
	}
	println!("\n/* functions */\n");
	for RegDTS { func, owner, ty, .. } in dts.iter() {
		if owner.is_none() && *ty == DtsTy::Func {
			println!("{}", func());
		}
	}
	let mut clases = CLASSES.iter().collect::<Vec<_>>();
	clases.sort_by(|a, b| {
		a.name
			.to_str()
			.unwrap()
			.to_lowercase()
			.cmp(&b.name.to_str().unwrap().to_lowercase())
	});
	println!("\n/* classes */\n");
	for cls in clases.iter() {
		if cls.interface {
			println!("export declare interface {} {{", cls.name.to_str().unwrap());
		} else {
			println!("export declare class {} {{", cls.name.to_str().unwrap());
		}
		for RegDTS { func, owner, ty, .. } in dts.iter() {
			if *ty != DtsTy::Constructor {
				continue;
			}
			if *owner == Some(cls.id) {
				println!("\t{}", func());
			}
		}
		println!("");
		for RegDTS { func, owner, ty, .. } in dts.iter() {
			if *ty == DtsTy::Constructor {
				continue;
			}
			if *owner == Some(cls.id) {
				println!("\t{}", func());
			}
		}
		println!("}}\n");
	}
}

/// Registers all linked components to the module exports
pub fn module_register_linkme<'env>(env: &'env Env, exports: &'env Value<'env>) -> &'env Value<'env> {
	let properties = FUNCTIONS
		.iter()
		.map(|v| {
			log::info!("napih function {:?}", v.name);
			PropertyDescriptor::default().name_cstr(v.name).method(v.func)
		})
		.chain(CONSTS.iter().filter_map(|v| {
			let value = (v.func)(env).ok()?.raw();
			log::info!("napih const {:?}", v.name);
			Some(PropertyDescriptor::default().name_cstr(v.name).value(value))
		}))
		.chain(CLASSES.iter().filter_map(|cls| {
			let constructors = METHODS
				.iter()
				.filter(|v| v.is_for(&cls.id))
				.filter_map(|v| v.constructor())
				.collect::<Vec<_>>();
			let properties = {
				let mut properties = HashMap::new();
				METHODS
					.iter()
					.filter(|v| v.is_for(&cls.id))
					.filter(|v| v.constructor().is_none())
					.for_each(|v| {
						let name = v.name();
						log::info!("napih {:?}::{:?}", cls.name, name);
						let prop = properties.remove(name).unwrap_or_else(|| PropertyDescriptor::default());
						let prop = v.property(prop, env);
						properties.insert(name, prop);
					});
				properties.into_iter().map(|v| v.1).collect::<Vec<_>>()
			};
			let Some(define) = env.define_class(cls.name, cls.func, &properties).throw(env) else {
				return None;
			};
			let Some(define_ref) = define.to().throw(env) else {
				return None;
			};
			define_class(cls.id, define_ref, constructors);
			Some(PropertyDescriptor::default().name_cstr(cls.name).value(define.raw()))
		}))
		.collect::<Vec<_>>();
	exports.define(&properties).throw(env);
	exports
}

impl RegMethod {
	/// Checks if this method belongs to the given type ID
	pub fn is_for(&self, fid: &TypeId) -> bool {
		match self {
			RegMethod::Constructor { id, .. } => fid == id,
			RegMethod::Method { id, .. } => fid == id,
			RegMethod::Value { id, .. } => fid == id,
			RegMethod::Static { id, .. } => fid == id,
			RegMethod::Getter { id, .. } => fid == id,
			RegMethod::Setter { id, .. } => fid == id,
		}
	}

	/// Gets the constructor callback if this is a constructor
	pub fn constructor(&self) -> Option<sys::napi_callback> {
		match self {
			RegMethod::Constructor { func, .. } => Some(*func),
			_ => None,
		}
	}

	/// Gets the method name
	pub fn name(&self) -> &'static core::ffi::CStr {
		match self {
			RegMethod::Constructor { .. } => unreachable!(),
			RegMethod::Method { name, .. } => name,
			RegMethod::Value { name, .. } => name,
			RegMethod::Static { name, .. } => name,
			RegMethod::Getter { name, .. } => name,
			RegMethod::Setter { name, .. } => name,
		}
	}

	/// Configures a property descriptor for this method
	pub fn property<'val>(&self, prop: PropertyDescriptor<'val>, env: &Env) -> PropertyDescriptor<'val> {
		match self {
			RegMethod::Constructor { .. } => prop,
			RegMethod::Method { name, func, .. } => {
				prop.name_cstr(name).method(*func) //
			}
			RegMethod::Value { name, func, .. } => match func(env) {
				Ok(v) => prop.name_cstr(name).value(v.raw()).attrs(sys::napi_static),
				Err(e) => {
					log::error!("RegMethod::Value  create failed:{}", e);
					prop
				}
			},
			RegMethod::Static { name, func, .. } => {
				prop.name_cstr(name).method(*func).attrs(sys::napi_static) //
			}
			RegMethod::Getter { name, func, .. } => {
				prop.name_cstr(name).getter(*func) //
			}
			RegMethod::Setter { name, func, .. } => {
				prop.name_cstr(name).setter(*func) //
			}
		}
	}
}

/// Represents a N-API module
#[repr(transparent)]
pub struct Module<'module> {
	inner: sys::napi_module,
	_mark: core::marker::PhantomData<&'module ()>,
}

impl<'module> Default for Module<'module> {
	fn default() -> Self {
		Self {
			inner: sys::napi_module {
				nm_version: 1,
				nm_filename: core::ptr::null_mut(),
				nm_flags: 0,
				nm_modname: core::ptr::null_mut(),
				nm_priv: core::ptr::null_mut(),
				nm_register_func: None,
				reserved: [core::ptr::null_mut() as *mut _; 4],
			},
			_mark: Default::default(),
		}
	}
}

impl<'module> Module<'module> {
	/// Gets the raw napi_module pointer
	pub fn as_napi(&self) -> *mut sys::napi_module {
		self as *const Self as *const core::ffi::c_void as *mut core::ffi::c_void as _
	}

	/// Sets the module version
	pub fn version(mut self, v: i32) -> Self {
		self.inner.nm_version = v;
		self
	}

	/// Sets the module flags
	pub fn flags(mut self, v: u32) -> Self {
		self.inner.nm_flags = v;
		self
	}

	/// Sets the module filename
	pub fn file_cstr(mut self, v: &'static core::ffi::CStr) -> Self {
		self.inner.nm_filename = v.as_ptr();
		self
	}

	/// Sets the module name
	pub fn name_cstr(mut self, v: &'static core::ffi::CStr) -> Self {
		self.inner.nm_modname = v.as_ptr();
		self
	}

	/// Sets the module registration function
	pub fn func(mut self, func: sys::napi_addon_register_func) -> Self {
		self.inner.nm_register_func = func;
		self
	}

	/// Sets the module private data
	pub fn data(mut self, data: *mut core::ffi::c_void) -> Self {
		self.inner.nm_priv = data;
		self
	}

	/// Registers the module with N-API
	pub fn regist(&self) {
		unsafe { sys::napi_module_register(self.as_napi()) }
	}
}
